package de.bht.fb6.cg1.exercise3.implement;

import de.bht.fb6.cg1.exercise3.ColumnVector;
import de.bht.fb6.cg1.exercise3.Matrix;

/**
 * provides basic transformations
 */
public class Transform {

	/**
	 * create a vector to move a vector
	 * @param move direction to move
	 * @return a vector that can moves another vector.
	 */
	public static Matrix<Double> translation(final ColumnVector<Double> move) {
		final Double[][] values = new Double[move.getRows()+1][move.getRows()+1];
		for (int rows=0; rows<move.getRows(); rows++) {
			for (int cols=0; cols<move.getRows(); cols++) {
				if (rows==cols) {
					values[rows][cols] = 0.;
				} else if (cols == move.getRows()-1) {
					values[rows][cols] = move.get(0, cols);
				} else {
					values[rows][cols] = 1.;
				}
			}
		}
		return new MatrixImpl<Double>(values, DoubleRing.class);
	}

	/**
	 * create a vector to move a vector
	 * @param move direction to move
	 * @return a vector that can moves another vector.
	 */
	public static Matrix<Float> translationFloat(final ColumnVector<Float> move) {
		final Float[][] values = new Float[move.getRows()+1][move.getRows()+1];
		for (int rows=0; rows<move.getRows(); rows++) {
			for (int cols=0; cols<move.getRows(); cols++) {
				if (rows==cols) {
					values[rows][cols] = 0f;
				} else if (cols == move.getRows()-1) {
					values[rows][cols] = move.get(0, cols);
				} else {
					values[rows][cols] = 1f;
				}
			}
		}

		return new MatrixImpl<Float>(values, FloatRing.class);
	}

	/**
	 * rotates a 2 dimensional vector
	 * @param angle degree to rotate
	 * @return a vector to rotate other vertices
	 */
	public static Matrix<Double> rotate2d(final Double angle) {
		final Double[][] mult = new Double[][] {
				{1.,0.,0.},
				{0.,Math.cos(angle), -1 * Math.sin(angle)},
				{0.,Math.sin(angle), Math.cos(angle)}
		};

		return new MatrixImpl<Double>(mult, DoubleRing.class);
	}

	/**
	 * rotates a 2 dimensional vector
	 * @param angle degree to rotate
	 * @return a vector to rotate other vertices
	 */
	public static Matrix<Float> rotate2d(final Float angle) {
		final Float[][] mult = new Float[][] {
				{1f,0f,0f},
				{0f,new Float(Math.cos(angle)), -1 * new Float(Math.sin(angle))},
				{0f,new Float(Math.sin(angle)), new Float(Math.cos(angle))}
		};

		return new MatrixImpl<Float>(mult, FloatRing.class);
	}

	/**
	 * rotates a 3 dimensional vector
	 * @param x rotate on x axis
	 * @param y rotate on y axis
	 * @param z rotate on z axis
	 * @return rotate matrix
	 */
	public static Matrix<Double> rotate3d(final Double x, final Double y, final Double z) {
		final Double[][] xangle = new Double[][] {
				{1.,0.,0.,0.},
				{0.,Math.cos(x), -1 * Math.sin(x),0.},
				{0.,Math.sin(x), Math.cos(x),0.},
				{0.,0.,0.,1.}
		};
		final Double[][] yangle = new Double[][] {
				{Math.cos(y),0.,Math.sin(y),0.},
				{0.,1.,0.,0.},
				{-1.*Math.sin(y),0., Math.cos(y),0.},
				{0.,0.,0.,1.}
		};
		final Double[][] zangle = new Double[][] {
				{Math.cos(z), -1 * Math.sin(z), 0.,0.},
				{Math.sin(z), Math.cos(z), 0.,0.},
				{0.,0.,1.,0.},
				{0.,0.,0.,1.}
		};
		return new MatrixImpl<Double>(xangle, DoubleRing.class).mult(
				new MatrixImpl<Double>(yangle, DoubleRing.class).mult(
						new MatrixImpl<Double>(zangle, DoubleRing.class)));
	}
	
	/**
	 * rotates a 3 dimensional vector
	 * @param x rotate on x axis
	 * @param y rotate on y axis
	 * @param z rotate on z axis
	 * @return rotate matrix
	 */
	public static Matrix<Float> rotate3d(final Float x, final Float y, final Float z) {
		final Float[][] xangle = new Float[][] {
				{1f,0f,0f,0f},
				{0f,new Float(Math.cos(x)), -1 * new Float(Math.sin(x)),0f},
				{0f,new Float(Math.sin(x)), new Float(Math.cos(x)),0f},
				{0f,0f,0f,1f}
		};
		final Float[][] yangle = new Float[][] {
				{new Float(Math.cos(y)),0f,new Float(Math.sin(y)),0f},
				{0f,1f,0f,0f},
				{-1*new Float(Math.sin(y)),0f, new Float(Math.cos(y)),0f},
				{0f,0f,0f,1f}
		};
		final Float[][] zangle = new Float[][] {
				{new Float(Math.cos(z)), -1 * new Float(Math.sin(z)), 0f,0f},
				{new Float(Math.sin(z)), new Float(Math.cos(z)), 0f,0f},
				{0f,0f,1f,0f},
				{0f,0f,0f,1f}
		};
		return new MatrixImpl<Float>(xangle, FloatRing.class).mult(
				new MatrixImpl<Float>(yangle, FloatRing.class).mult(
						new MatrixImpl<Float>(zangle, FloatRing.class)));
	}

	/**
	 * provides a scale matrix
	 * @param mod scale ratio
	 * @return scale matrix
	 */
	public static Matrix<Double> scale(final Double... mod) {
		final Double[][] vals = new Double[mod.length+1][mod.length+1];
		for (int rows = 0; rows < mod.length+1; rows++) {
			for (int cols = 0; cols < mod.length+1; cols++) {
				if (cols == rows) {
					if (cols < mod.length) {
						vals[rows][cols] = mod[cols];
					} else {
						vals[rows][cols] = 1.;
					}
				} else {
					vals[rows][cols] = 0.;
				}
			}
		}
		return new MatrixImpl<Double>(vals, DoubleRing.class);
	}

	/**
	 * provides a scale matrix
	 * @param mod scale ratio
	 * @return scale matrix
	 */
	public static Matrix<Float> scale(final Float... mod) {
		final Float[][] vals = new Float[mod.length+1][mod.length+1];
		for (int rows = 0; rows < mod.length+1; rows++) {
			for (int cols = 0; cols < mod.length+1; cols++) {
				if (cols == rows) {
					if (cols < mod.length) {
						vals[rows][cols] = mod[cols];
					} else {
						vals[rows][cols] = 1f;
					}
				} else {
					vals[rows][cols] = 0f;
				}
			}
		}
		return new MatrixImpl<Float>(vals, FloatRing.class);
	}

	/**
	 * provides a 2d shear
	 * @param x shear on x axis
	 * @param y shear on y axis
	 * @return shear matrix
	 */
	public static Matrix<Double> shear(final Double x, final Double y) {
		final Double[][] vals = {
				{1., x , 0.},
				{y , 1., 0.},
				{0., 0., 1.}
		};
		return new MatrixImpl<Double>(vals, DoubleRing.class);
	}

	/**
	 * provides a 2d shear matrix
	 * @param x shear on x axis
	 * @param y shear on y axis
	 * @return shear matrix
	 */
	public static Matrix<Float> shear(final Float x, final Float y) {
		final Float[][] vals = {
				{1f, x,  0f},
				{y,  1f, 0f},
				{0f, 0f, 1f}
		};
		return new MatrixImpl<Float>(vals, FloatRing.class);
	}

	/**
	 * provides a 3d shear matrix
	 * 
	 * @param xy shear on xy
	 * @param xz shear on xz
	 * @param yx shear on yx
	 * @param yz shear on yz
	 * @param zx shear on zx
	 * @param zy shear on zy
	 * @return a shear matrix to the given shears
	 */
	public static Matrix<Double> shear(final Double xy, final Double xz, final Double yx, final Double yz, final Double zx, final Double zy ) {
		final Double[][] vals = {
				{1., xy, xz, 0.},
				{yx, 1., yz, 0.},
				{zx, zy, 1., 0.},
				{0., 0., 0., 1.}
		};
		return new MatrixImpl<Double>(vals, DoubleRing.class);
	}

	/**
	 * provides a 3d shear matrix
	 * 
	 * @param xy shear on xy
	 * @param xz shear on xz
	 * @param yx shear on yx
	 * @param yz shear on yz
	 * @param zx shear on zx
	 * @param zy shear on zy
	 * @return a shear matrix to the given shears
	 */
	public static Matrix<Float> shear(final Float xy, final Float xz, final Float yx, final Float yz, final Float zx, final Float zy ) {
		final Float[][] vals = {
				{1f, xy, xz, 0f},
				{yx, 1f, yz, 0f},
				{zx, zy, 1f, 0f},
				{0f, 0f, 0f, 1f}
		};
		return new MatrixImpl<Float>(vals, FloatRing.class);
	}
}
